package com.zws.cucumber.var;

import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;

import java.util.HashMap;
import java.util.Map;

public class ZwsVarBlock {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsVarBlock.class);

    public static ZwsVarBlock push(ZwsVarBlock block, ZwsVarScope scope) {
        ZwsVarBlock newBlock = new ZwsVarBlock(block, scope);
        logger.debug("push variable block: {}, scope: {}", newBlock, newBlock.scope);
        return newBlock;
    }

    public static ZwsVarBlock pop(ZwsVarBlock block) {
        logger.debug("pop variable block: {}", block);
        ZwsVarBlock parent = block.getParent();
        block.parent = null;
        return parent;
    }

    private ZwsVarBlock parent;
    private ZwsVarScope scope;
    private Map<String, Object> variables;
    private ZwsVarBlockContext context;

    public ZwsVarBlock(ZwsVarBlock parent, ZwsVarScope scope, Map<String, Object> variables) {
        this.parent = parent;
        this.scope = scope;
        this.variables = variables;
    }

    public ZwsVarBlock(ZwsVarBlock parent, ZwsVarScope scope) {
        this(parent, scope, new HashMap<>());
    }

    public ZwsVarBlock(ZwsVarScope scope) {
        this(null, scope);
    }

    public ZwsVarBlock getParent() {
        return parent;
    }

    public ZwsVarScope getScope() {
        return scope;
    }

    public ZwsVarBlockContext getContext() {
        return context;
    }

    public void setContext(ZwsVarBlockContext context) {
        this.context = context;
    }

    public Object put(String key, Object val) {
        if (!NumberUtils.isNumber(key)) {
            scope.checkBeforePutting(this, key, val);
            return variables.put(key, val);
        } else {
            throw new IllegalArgumentException("variable name should not be number");
        }
    }

    public void putAll(Map<? extends String, ?> toMerge) {
        toMerge.entrySet().forEach(en -> {
            if (NumberUtils.isNumber(en.getKey())) {
                throw new IllegalArgumentException("variable name should not be number");
            }
            scope.checkBeforePutting(this, en.getKey(), en.getValue());
        });
        variables.putAll(toMerge);
    }

    public <T> T get(String key) {
        Pair<T, ZwsVarScope> pair = getRecursively(key);
        if (pair != null) {
            return pair.getKey();
        }
        return null;
    }

    public <T> T getOfDefault(String key, T defaultVal) {
        T val = get(key);
        if (val != null) {
            return val;
        }
        return defaultVal;
    }

    @SuppressWarnings("unchecked")
    <T> Pair<T, ZwsVarScope> getRecursively(String key) {
        T ret = (T) variables.get(key);
        if (ret != null) {
            return Pair.of(ret, scope);
        }
        if (parent != null) {
            return parent.getRecursively(key);
        }
        return null;
    }

    <T> Pair<T, ZwsVarScope> getShallowly(String key) {
        T ret = (T) variables.get(key);
        if (ret != null) {
            return Pair.of(ret, scope);
        }
        return null;
    }

    public boolean containsKey(String key) {
        return containsKeyRecursively(key);
    }

    boolean containsKeyRecursively(String key) {
        boolean contained = variables.containsKey(key);
        if (!contained && parent != null) {
            return parent.containsKeyRecursively(key);
        }
        return contained;
    }

    boolean containsKeyShallowly(String key) {
        return variables.containsKey(key);
    }

    public boolean containsValue(Object val) {
        return containsValueRecursively(val);
    }

    boolean containsValueRecursively(Object val) {
        boolean contained = variables.containsValue(val);
        if (!contained && parent != null) {
            return parent.containsValueRecursively(val);
        }
        return contained;
    }

    boolean containsValueShallowly(Object val) {
        return variables.containsValue(val);
    }

    Object remove(String key) {
        return variables.remove(key);
    }

    public int size() {
        return sizeRecursively();
    }

    int sizeRecursively() {
        int size = variables.size();;
        if (parent != null) {
            return parent.sizeRecursively() + size;
        }
        return size;
    }

    public int sizeShallowly() {
        return variables.size();
    }

    public boolean isEmpty() {
        return isEmptyRecursively();
    }

    boolean isEmptyRecursively() {
        boolean isEmpty = variables.isEmpty();
        if (isEmpty && parent != null) {
            return parent.isEmptyRecursively();
        }
        return isEmpty;
    }

    public boolean isEmptyShallowly() {
        return variables.isEmpty();
    }

    public Map<String, Object> getVariables() {
        Map<String, Object> merged = new HashMap<>();
        mergeVariablesRecursively(merged);
        return merged;
    }

    void mergeVariablesRecursively(Map<String, Object> merged) {
        if (parent != null) {
            parent.mergeVariablesRecursively(merged);
        }
        merged.putAll(variables);
    }

    public Map<String, Object> getCopyOfShallowVariables() {
        return Map.copyOf(variables);
    }

    Map<String, Object> getShallowVariables() {
        return variables;
    }
}
